package com.hqd.ch03.v22.aop.framework;

import com.hqd.ch03.v22.aop.Advised;
import com.hqd.ch03.v22.aop.SpringImitationProxy;
import com.hqd.ch03.v22.aop.TargetClassAware;
import com.hqd.ch03.v22.aop.support.AopUtils;
import com.hqd.ch03.v22.core.DecoratingProxy;
import com.hqd.ch03.v22.utils.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public abstract class AopProxyUtils {
    public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {
        return completeProxiedInterfaces(advised, false);
    }

    public static Class<?> ultimateTargetClass(Object candidate) {
        Object current = candidate;
        Class<?> result = null;
        while (current instanceof TargetClassAware) {
            result = ((TargetClassAware) current).getTargetClass();
        }
        if (result == null) {
            result = (AopUtils.isCglibProxy(candidate) ? candidate.getClass().getSuperclass() : candidate.getClass());
        }
        return result;
    }

    static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
        Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
        if (specifiedInterfaces.length == 0) {
            Class<?> targetClass = advised.getTargetClass();
            if (targetClass != null) {
                if (targetClass.isInterface()) {//目标类是接口
                    advised.setInterfaces(targetClass);
                } else if (Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {//类、lambda就添加对应的接口
                    advised.setInterfaces(targetClass.getInterfaces());
                }
                specifiedInterfaces = advised.getProxiedInterfaces();
            }
        }
        List<Class<?>> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3);
        proxiedInterfaces.addAll(Arrays.asList(specifiedInterfaces));
        /**
         * 添加标记接口
         */
        if (!advised.isInterfaceProxied(SpringImitationProxy.class)) {
            proxiedInterfaces.add(SpringImitationProxy.class);
        }
        /**
         * 目标类可见，且不包含Advised接口
         */
        if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) {
            proxiedInterfaces.add(Advised.class);
        }
        /**
         * 用于jdk代理中，获取对应的目标class
         */
        if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) {
            proxiedInterfaces.add(DecoratingProxy.class);
        }
        return proxiedInterfaces.toArray(new Class[0]);
    }

    public static boolean equalsInProxy(AdvisedSupport a, AdvisedSupport b) {
        return (a == b ||
                (equalsProxiedInterfaces(a, b) && equalsAdvisors(a, b) && a.getTargetSource().equals(b.getTargetSource())));
    }

    public static boolean equalsProxiedInterfaces(AdvisedSupport a, AdvisedSupport b) {
        return Arrays.equals(a.getProxiedInterfaces(), b.getProxiedInterfaces());
    }

    public static boolean equalsAdvisors(AdvisedSupport a, AdvisedSupport b) {
        return a.getAdvisorCount() == b.getAdvisorCount() && Arrays.equals(a.getAdvisors(), b.getAdvisors());
    }

    static Object[] adaptArgumentsIfNecessary(Method method, Object[] arguments) {
        if (ObjectUtils.isEmpty(arguments)) {
            return new Object[0];
        }
        if (method.isVarArgs()) {
            if (method.getParameterCount() == arguments.length) {
                Class<?>[] paramTypes = method.getParameterTypes();
                int varargIndex = paramTypes.length - 1;
                Class<?> varargType = paramTypes[varargIndex];
                if (varargType.isArray()) {
                    Object varargArray = arguments[varargIndex];
                    if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
                        Object[] newArguments = new Object[arguments.length];
                        System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
                        Class<?> targetElementType = varargType.getComponentType();
                        int varargLength = Array.getLength(varargArray);
                        Object newVarargArray = Array.newInstance(targetElementType, varargLength);
                        System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
                        newArguments[varargIndex] = newVarargArray;
                        return newArguments;
                    }
                }
            }
        }
        return arguments;
    }
}
